package com.cloud.tsp.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloud.api.constants.StrVoPage;
import com.cloud.api.domain.stats.Maintenance;
import com.cloud.api.myenum.AlarmTypeEnum;
import com.cloud.core.exception.MyException;
import com.cloud.api.domain.stats.QueryRecord;
import com.cloud.api.domain.stats.RecordList;
import com.cloud.api.domain.tsp.*;
import com.cloud.mybatis.domain.TableDataInfo;
import com.cloud.tsp.config.MysqlConfig;
import com.cloud.tsp.mapper.VehicleDataMapper;
import com.cloud.tsp.service.*;
import com.cloud.tsp.util.CoordTransformUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>
 * OLAP 服务实现类
 * </p>
 *
 * @author haohaibo
 * @since 2023-12-01
 */
@Service("vehicleDataService")
public class VehicleDataServiceImpl extends ServiceImpl<VehicleDataMapper, VehicleData> implements IVehicleDataService {

    @Resource
    private VehicleDataMapper vehicleDataMapper;
    @Resource
    private MessageSource messageSource;
    @Resource
    private IVehicleProductionService vehicleProductionService;
    @Resource
    private IVehicleLoginService vehicleLoginService;
    @Resource
    private IVehicleLogoutService vehicleLogoutService;
    @Resource
    private IMaintenanceService maintenanceService;

    @Value("${map.baidu.ak}")
    private String mapAk;
    @Autowired
    private MysqlConfig mysqlConfig;

//    @Value("${map.track.pageSize}")
    private Long pageSize=100L;


    /*@Override
    public TableDataInfo<VehicleLocation> selectTrackByVinTime(VinTimePeriod para) {
        para.setStartTimeDate(para.getStartTime().toLocalDate());
        Long count = vehicleDataMapper.selectTrackByVinTimeCount(para);
        long offset = Math.max((para.getPage() - 1) * para.getPageSize() - 50, 0L);
        long limit = para.getPageSize();
        if (para.getPage() > 1){
            limit = Math.min(para.getPageSize() + 100, count);
        }

        List<VehicleLocation> data = vehicleDataMapper.selectTrackByVinTime(offset,limit, para);

        List<VinLocation> list = new ArrayList<>();
        for (VehicleLocation vehicleLocation : data){
            VinLocation vinLocation = new VinLocation();
            vinLocation.setLoc_time(String.valueOf(vehicleLocation.getOpTime().toEpochSecond(ZoneOffset.UTC)));
            String lat = String.valueOf(vehicleLocation.getLat());
            String lng = String.valueOf(vehicleLocation.getLng());
            if (!"NORTH".equals(vehicleLocation.getLatitudeDataType())){
                lat = "-"+ lat;
            }
            if (!"EAST".equals(vehicleLocation.getLongitudeDataType())){
                lng = "-"+ lng;
            }
            vinLocation.setLatitude(lat);
            vinLocation.setLongitude(lng);
            vinLocation.setCoord_type_input("wgs84ll");
            list.add(vinLocation);
        }

        List<VehicleLocation> track = null;
        try {
            track = getTrack(list);
        } catch (Exception e) {
            e.printStackTrace();
            return TableDataInfo.build(data);
        }
        TableDataInfo<VehicleLocation> rspData = new TableDataInfo();
        rspData.setCode(200);
        rspData.setMsg("查询成功");
        rspData.setTotal(count);
        if (para.getPage() <= 1){
            rspData.setRows(track);
            return rspData;
        }

        int startIndex = 0;
        int endIndex = track.size();
        LocalDateTime leftOffset = data.get(50).getOpTime();
        LocalDateTime rightOffset = null;
        //定义是否超出大小
        boolean isOutOfSize = false;
        if (data.size() > (para.getPageSize()+50)){
            rightOffset = data.get((int) (para.getPageSize() + 50)).getOpTime();
            isOutOfSize = true;
        }

        for (int i = 0; i < track.size(); i++) {
            if (track.get(i).getOpTime().isAfter(leftOffset) && startIndex== 0){
                startIndex = i;
            }else if (isOutOfSize && track.get(i).getOpTime().isAfter(rightOffset)){
                endIndex = i;
                break;
            }else if (!isOutOfSize && startIndex != 0){
                break;
            }
        }
        List<VehicleLocation> locations = track.subList(startIndex, endIndex);
        rspData.setRows(locations);

        return rspData;
    }*/
    @Override
    public TableDataInfo<VehicleLocation> selectTrackByVinTime(VinTimePeriod para) {
        para.setStartTimeDate(para.getStartTime().toLocalDate());
        Long count = vehicleDataMapper.selectTrackByVinTimeCount(para);
        long offset = Math.max((para.getPage() - 1) * para.getPageSize() - 50, 0L);
        long limit = para.getPageSize();
        if (para.getPage() > 1){
            limit = Math.min(para.getPageSize() + 100, count);
        }

        List<VehicleLocation> data = vehicleDataMapper.selectTrackByVinTime(offset,limit, para);
        //使用第一个工具类
        CoordTransformUtil build = CoordTransformUtil.builder().build();
        List<double[]> list = new ArrayList<>();
        for (VehicleLocation vehicleLocation : data){
            double[] doubles = new double[2];
            doubles[0] = vehicleLocation.getLat().doubleValue();
            doubles[1] = vehicleLocation.getLng().doubleValue();
            list.add(doubles);
        }
        List<double[]> afterList = build.wgs2BD09List(list);
        for (int i = 0; i < data.size(); i++) {
            data.get(i).setLat(BigDecimal.valueOf(afterList.get(i)[0]));
            data.get(i).setLng(BigDecimal.valueOf(afterList.get(i)[1]));
        }

        return TableDataInfo.build(data);
    }

    @Override
    public TableDataInfo<VehicleLocation> selectTrackByVinTimeNoCorrect(StrVoPage para) {
        Page<VehicleLocation> page = new Page<>(para.getPage(), para.getPageSize());
        VinTimePeriod query = new VinTimePeriod();
        Maintenance byId = maintenanceService.getById(para.getId());
       /* query.setMinLat(new BigDecimal(byId.getMinLat()).multiply(new BigDecimal(1000000)).toPlainString());
        query.setMaxLat(new BigDecimal(byId.getMaxLat()).multiply(new BigDecimal(1000000)).toPlainString());
        query.setMinLng(new BigDecimal(byId.getMinLng()).multiply(new BigDecimal(1000000)).toPlainString());
        query.setMaxLng(new BigDecimal(byId.getMaxLng()).multiply(new BigDecimal(1000000)).toPlainString());*/
        query.setVin(byId.getVin());
        query.setStartTime(byId.getStartTime());
        query.setEndTime(byId.getEndTime());
        /*return vehicleDataMapper.selectTrackByVinTimeNoCorrect(para);
        System.out.println(para);*/
        IPage<VehicleLocation> data = vehicleDataMapper.selectTrackByVinTimeNoCorrect(page, query);
        return TableDataInfo.build(data);
    }

    public List<VehicleLocation> getTrack(List<VinLocation> list){
        String url = "https://api.map.baidu.com/rectify/v1/track";
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
        paramMap.add("ak", mapAk);
        paramMap.add("point_list", list);
        paramMap.add("rectify_option", "denoise_grade:1|vacuate_grade:0");
        List<VehicleLocation> locations = new ArrayList<>();
        try {
            ResponseEntity<String> response = new RestTemplate().postForEntity(url,paramMap, String.class);
            JSONObject resultJson = JSONUtil.parseObj(response.getBody());
            if (Integer.parseInt(resultJson.get("status").toString()) != 0) {
                throw new MyException(resultJson.get("message").toString());
            }
//            System.out.println(resultJson);
            JSONArray points = JSONUtil.parseArray(resultJson.get("points"));

            for (int i = 0; i < points.size(); i++) {
                JSONObject point = points.getJSONObject(i);
                VehicleLocation vehicleLocation = new VehicleLocation();
                vehicleLocation.setLat(new BigDecimal(String.valueOf(point.get("latitude"))));
                vehicleLocation.setLng(new BigDecimal(String.valueOf(point.get("longitude"))));
                long milliseconds = (int)point.get("loc_time") * 1000L;
                Instant instant = Instant.ofEpochMilli(milliseconds);
                LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
                vehicleLocation.setOpTime(dateTime);
                locations.add(vehicleLocation);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return locations;
    }

    @Override
    public VehicleDataVo selectVehicleDataByVin(String id) {
        Locale locale = LocaleContextHolder.getLocale();
        VehicleProduction vehicleProduction = vehicleProductionService.selectByVin1(id);
        if (vehicleProduction == null){
            return new VehicleDataVo();
        }
        VehicleDataVo data = vehicleDataMapper.selectVehicleDataByVin(vehicleProduction.getVin());
        if (data == null){
            return new VehicleDataVo();
        }
        handleAlarms(data);
        String codes = String.valueOf(data.getRechargeableSubsystemsCodes());
        Pattern pattern = Pattern.compile("\\[(.*?)\\]");
        Matcher matcher = pattern.matcher(codes);
        if (matcher.find()) {
            data.setRechargeableSubsystemsCodes(matcher.group(1));
        }else {
            data.setRechargeableSubsystemsCodes(null);
        }
        data.setIccid(vehicleProduction.getIccid());
        data.setBatteryPackCode(vehicleProduction.getBatteryPackCode());
        long between = ChronoUnit.SECONDS.between(data.getOpTime(),LocalDateTime.now());
        if (between >= mysqlConfig.getSecond() || between < 0){
            data.setVehicleState(messageSource.getMessage("vehicle.status.offline", null, locale));
        }else {
            data.setVehicleState(messageSource.getMessage("vehicle.status.online", null, locale));
        }
        Object[] cellVoltagesArray = getBigDecimals(data);
        data.setCellVoltages(Arrays.toString(cellVoltagesArray));
        //翻译地址
        String location = getLocation(data.getLat(), data.getLng());
        data.setAddr(location);
        return data;
    }

    public String getLocation(BigDecimal latData,BigDecimal lngData){
        String address = "translate error";
        String lat = latData.toString();
        String lng = lngData.toString();
        String ak = mapAk;
        String url = "https://%s/reverse_geocoding/v3/?ak=%s&output=json&extensions_poi=1&coordtype=wgs84ll&location=%s,%s";
        url = String.format(url,"api.map.baidu.com",ak,lat,lng);
        try {
            ResponseEntity<String> response = new RestTemplate().getForEntity(url, String.class);
            JSONObject resultJson = JSONUtil.parseObj(response.getBody());
            if (Integer.parseInt(resultJson.get("status").toString()) != 0) {
                throw new MyException(resultJson.get("message").toString());
            }
            JSONObject result = resultJson.getJSONObject("result");
            if (result.getStr("formatted_address_poi")!= null){
                address = result.getStr("formatted_address_poi");
            }else {
                address = result.getStr("formatted_address");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return address;
    }

    @Override
    public LockStatus selectVehicleData(String vin) {
        Locale locale = LocaleContextHolder.getLocale();
        LockStatus lockStatus = vehicleDataMapper.selectVehicleData(vin);
        if (lockStatus == null){
            return null;
        }
        long between = ChronoUnit.SECONDS.between(lockStatus.getOpTime(),LocalDateTime.now());
        if (between >= mysqlConfig.getSecond()) {
            lockStatus.setOnlineStatus(messageSource.getMessage("vehicle.status.offline", null, locale));
        }else {
            lockStatus.setOnlineStatus(messageSource.getMessage("vehicle.status.online", null, locale));
        }
        return lockStatus;
    }


    @Override
    public List<LocalDateTime> existVinDataTime(VinTimePeriod para) {
        return vehicleDataMapper.existVinDataTime(para);
    }

    @Override
    public TableDataInfo<RecordList> selectRecord(QueryRecord para) {
        getDate(para);
//        getPermission(para);
        TableDataInfo<RecordList> tableDataInfo = new TableDataInfo<>();
        switch (para.getRecordType()){
            case "1" :
                //车辆登入
                tableDataInfo = vehicleLoginService.selectLoginRecord(para);
                break;
            case "2" :
                //实时数据
                para.setRecordType("realtime_data_reporting");
                tableDataInfo = this.selectRealTimeRecord(para);
                break;
            case "3" :
                //补发数据
                para.setRecordType("reissiue_data");
                tableDataInfo = this.selectRealTimeRecord(para);
                break;
            case "4" :
                //车辆登出
                tableDataInfo = vehicleLogoutService.selectLogoutRecord(para);
                break;
            default :
                throw new MyException("wds.exception.recordType");
        }
        for (RecordList recordList : tableDataInfo.getRows()){
            recordList.setVin(StrUtil.hide(recordList.getVin(), 8, 11));
        }
        return tableDataInfo;
    }

    /*public void getPermission(QueryRecord para){
        para.setDataScope(LoginHelper.getDataScope());
        if (para.getDataScope() == 5){
            R<SystemRoleDto> roleDetail = systemRoleOpenFeignService.getById(new StrVo(LoginHelper.getUserLogin().getRoleId()));
            String[] split = roleDetail.getData().getDeptIds().split(",");
            List<String> deptIds = new ArrayList<>(Arrays.asList(split));
            para.setDeptIds(deptIds);
        }else {
            List<String> list = new ArrayList<>();
            list.add(LoginHelper.getUserLogin().getDeptId());
            para.setDeptIds(list);
        }
    }*/

    @Override
    public VehicleDataVo selectRecordDetail(QueryRecord para) {
        switch (para.getRecordType()){
            case "2" :
                //实时数据
                para.setRecordType("realtime_data_reporting");
                break;
            case "3" :
                //补发数据
                para.setRecordType("reissiue_data");
                break;
        }
        VehicleDataVo vehicleDataVo = vehicleDataMapper.selectRecordDetail(para);
        Optional.ofNullable(vehicleDataVo).orElseThrow(() -> new MyException("wds.exception.id"));
        Object[] cellVoltagesArray = getBigDecimals(vehicleDataVo);
        handleSubsystemTemperatures(vehicleDataVo);
        vehicleDataVo.setCellVoltages(Arrays.toString(cellVoltagesArray));
        vehicleDataVo.setRecordLength(vehicleDataVo.getRecord().length());
        vehicleDataVo.setRecord(vehicleDataVo.getRecord().toUpperCase());
        handleAlarms(vehicleDataVo);
        return vehicleDataVo;
    }

    private void handleAlarms(VehicleDataVo vehicleDataVo) {
        Long alarmInfo = vehicleDataVo.getAlarmInfo();
        if (alarmInfo == null){
            return;
        }
        List<String> alarmInfoList = new ArrayList<>();
        AlarmTypeEnum[] values = AlarmTypeEnum.values();
        for(int i = 31; i>=0 ; i--){
            if ((alarmInfo & (1L << i)) != 0){
                if (i < values.length) {
                    alarmInfoList.add(values[i].getDesc());
                }else {
                    alarmInfoList.add("解析错误");
                    break;
                }
            }
        }
        vehicleDataVo.setAlarmInfoList(alarmInfoList);
    }

    public void handleSubsystemTemperatures(VehicleDataVo vehicleDataVo){
        Object object = vehicleDataVo.getSubsystemTemperatures();
        JSONArray objects = JSONUtil.parseArray(object);
        Object[] subsystemTemperaturesArray = new Object[objects.size()];
        for (int i = 0; i < objects.size(); i++) {
            int anInt = objects.getInt(i);
            if (anInt == 254 || anInt == 255){
                if (anInt == 254){
                    subsystemTemperaturesArray[i] = "异常";
                }else {
                    subsystemTemperaturesArray[i] = "无效";
                }
            }else {
                subsystemTemperaturesArray[i] = String.valueOf(anInt);
            }
        }
        vehicleDataVo.setSubsystemTemperatures(Arrays.toString(subsystemTemperaturesArray));
    }

    @NotNull
    private static Object[] getBigDecimals(VehicleDataVo vehicleDataVo) {
        JSONArray objects = JSONUtil.parseArray(vehicleDataVo.getCellVoltages());
        Object[] cellVoltagesArray = new Object[objects.size()];
        for (int i = 0; i < objects.size(); i++) {
            BigDecimal anInt = objects.getBigDecimal(i);
            if (anInt.compareTo(new BigDecimal("25534")) == 0 || anInt.compareTo(new BigDecimal("25535")) == 0){
                Object object;
                if (anInt.compareTo(new BigDecimal("25534")) == 0){
                    object = "异常";
                }else {
                    object = "无效";
                }
                cellVoltagesArray[i] = object;
            }else {
                BigDecimal v = anInt.multiply(new BigDecimal("0.001"));
                cellVoltagesArray[i] = v.toString();
            }
        }
        return cellVoltagesArray;
    }

    public void getDate(QueryRecord para){
        if (para.getStartTime() == null || para.getEndTime() ==null){
            LocalDateTime now = LocalDateTime.now();
            para.setStartTime(now.minus(1, ChronoUnit.DAYS));
            para.setEndTime(now);
        }
        long between = ChronoUnit.DAYS.between(para.getStartTime(), para.getEndTime());
        if (between >= 31){
            throw new MyException("wds.exception.dateRange");
        }
    }

    public TableDataInfo<RecordList>selectRealTimeRecord(QueryRecord para){
        Page<RecordList> page = new Page<>();
        page.setCurrent(para.getPage());
        page.setSize(para.getPageSize());
        IPage<RecordList> data = vehicleDataMapper.selectRealTimeRecord(page, para);
        return TableDataInfo.build(data);
    }
}